package v3.maps;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;

import v2.simpleUi.util.SimpleAsyncTask;
import v3.maps.SimpleGestureListener.SimpleTouchEventInterface;
import android.graphics.Point;
import android.location.Location;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentTransaction;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.widget.FrameLayout;

import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMap.OnMarkerClickListener;
import com.google.android.gms.maps.GoogleMap.OnMarkerDragListener;
import com.google.android.gms.maps.GoogleMap.OnMyLocationChangeListener;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;

public class GoogleMapsV2View extends SupportMapFragment implements I_MapView,
		SimpleTouchEventInterface, OnMarkerClickListener, OnMarkerDragListener {

	public interface MapsV2EventListener extends OnMyLocationChangeListener {
		/**
		 * now methods like
		 * {@link GoogleMapsV2View#initDefaultBehavior(GoogleMapsV2View)} can be
		 * called
		 * 
		 * @param googleMapsV2View
		 * @param activity
		 * @param mapController
		 */
		void onMapViewIsReadyForTheFirstTime(GoogleMapsV2View googleMapsV2View,
				FragmentActivity activity, GoogleMap mapController);

		void onNewAreaOnMapIsShown(LatLng topLeft, LatLng bottomRight,
				int zoomLevel);

		void onLongPress(FragmentActivity activity, GoogleMap map, LatLng gpsPos);

		void onSingleTab(FragmentActivity activity, GoogleMap map, LatLng gpsPos);

		void onDoubleTab(FragmentActivity activity, GoogleMap map, LatLng gpsPos);
	}

	public interface MarkerListener {

		int DRAG_START = 1;
		int DRAG = 2;
		int DRAG_END = 3;

		boolean onTab(Marker marker);

		/**
		 * @param marker
		 * @param type
		 *            can be {@link MarkerListener#DRAG_START},
		 *            {@link MarkerListener#DRAG} or
		 *            {@link MarkerListener#DRAG_END}
		 */
		void onDrag(Marker marker, int type);

	}

	public static class Overlay extends LinkedHashSet<Marker> {

		private Integer defaultIconId;
		private GoogleMapsV2View googleMap;

		public Overlay(GoogleMapsV2View googleMap) {
			this.googleMap = googleMap;
		}

		public Overlay(GoogleMapsV2View googleMap, Integer defaultIconId) {
			this(googleMap);
			this.defaultIconId = defaultIconId;
		}

		@Override
		public void clear() {
			super.clear();
			removeAllMarkersWhichAreIn(this);
			googleMap.listeners.clear();
		}

		@Deprecated
		public boolean add(MarkerOptions markerOptions) {
			OverlayOptions.setIconIfNeeded(markerOptions, defaultIconId);
			if (googleMap == null) {
				Log.w(LOG_TAG, "googleMap was null, cant add new marker");
				return false;
			}
			return add(googleMap.getMap().addMarker(markerOptions));
		}

		@Override
		public boolean remove(Object object) {
			if (contains(object)) {
				((Marker) object).remove();
			}
			googleMap.listeners.remove(object);
			return super.remove(object);
		}

		private void removeAllMarkersWhichAreIn(Overlay o) {
			for (Marker marker : o) {
				marker.remove();
			}
		}

		public Marker add(LatLng pos, int markerDrawableId,
				MarkerListener markerListener) {

			if (googleMap.listeners.containsValue(markerListener)) {
				Log.e(LOG_TAG,
						"Element with same listener was already in the set, will not be added");
				return null;
			}

			Marker marker = googleMap.getMap().addMarker(
					new MarkerOptions().position(pos)
							.icon(BitmapDescriptorFactory
									.fromResource(defaultIconId)));
			googleMap.listeners.put(marker, markerListener);
			add(marker);
			return marker;

		}

		public Marker add(LatLng pos, MarkerListener newWaveMarkerListener) {
			return this.add(pos, defaultIconId, newWaveMarkerListener);
		}
	}

	@Deprecated
	public static class OverlayOptions extends ArrayList<MarkerOptions> {
		private Integer defaultIconId;

		public OverlayOptions() {
		}

		public OverlayOptions(Integer defaultIconId) {
			this.defaultIconId = defaultIconId;
		}

		@Override
		public void add(int index, MarkerOptions marker) {
			setIconIfNeeded(marker, defaultIconId);
			super.add(index, marker);
		}

		@Override
		public boolean add(MarkerOptions marker) {
			setIconIfNeeded(marker, defaultIconId);
			return super.add(marker);
		}

		protected static void setIconIfNeeded(MarkerOptions marker,
				Integer iconId) {
			if (iconId != null && marker.getIcon() == null) {
				marker.icon(BitmapDescriptorFactory.fromResource(iconId));
			}
		}
	}

	private static final String LOG_TAG = "GoogleMapsV2View";

	private FrameLayout container;
	private ArrayList<Overlay> overlays;
	private boolean longPressEnanabled = true;
	private GestureDetector myGestureDetector;
	/**
	 * If the screen is rotated e.g. the
	 * {@link GoogleMapsV2View#onViewCreated(View, Bundle)} method is called
	 * again. this flag can be used to reinitialize the map
	 */
	private boolean firstTimeThisMapIsShown = true;
	private HashMap<Marker, MarkerListener> listeners = new HashMap<Marker, GoogleMapsV2View.MarkerListener>();

	private MapsV2EventListener eventListener;

	private static Handler myHandler = new Handler(Looper.getMainLooper());

	public GoogleMapsV2View(FragmentActivity a, MapsV2EventListener l) {
		this.eventListener = l;
		int MAPS_CONTAINER_ID = 1680234530;
		container = new FrameLayout(a) {
			@Override
			public boolean dispatchTouchEvent(MotionEvent event) {
				/*
				 * block scolling of parents when the view is touched
				 */
				getParent().requestDisallowInterceptTouchEvent(true);

				if (event.getAction() == MotionEvent.ACTION_UP) {
					onNewAreaOnMapIsShown();
				}

				/*
				 * to understand the idea of redirecting the events look at the
				 * MyGestureListener class which will inform the map when a
				 * special event like a tab, doubleTab, LongPress, .. happened
				 */
				if (!super.dispatchTouchEvent(event)) {
					/*
					 * first the map should be asked to handle the scroll event
					 * on its own, if it is not clickable this part here is
					 * called and false can be returned directly, if it is
					 * clickable the event will be passed on to the
					 * myGestureDetector
					 * 
					 * the map is disabled in this case so return false:
					 */
					return false;
				}
				// else pass the event to the gesture detector
				return myGestureDetector.onTouchEvent(event);

			}

		};

		container.setId(MAPS_CONTAINER_ID);
		container.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
				LayoutParams.MATCH_PARENT));
		FragmentTransaction ft = a.getSupportFragmentManager()
				.beginTransaction();
		ft.add(MAPS_CONTAINER_ID, this).commit();

		// create a GestureDetector:
		myGestureDetector = new GestureDetector(a, new SimpleGestureListener(
				this));
		myGestureDetector.setIsLongpressEnabled(longPressEnanabled);

	}

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		// to restore the fragment (e.g. when the screen is rotated):
		setRetainInstance(true);
	}

	public FrameLayout getContainerView() {
		return container;
	}

	@Override
	public LatLng getLatLngForPixelPos(int xPixelPosInMapView,
			int yPixelPosInMapView) {
		return getMap().getProjection().fromScreenLocation(
				new Point(xPixelPosInMapView, yPixelPosInMapView));
	}

	@Override
	public boolean setMapCenterTo(final LatLng pos) {
		if (pos != null) {
			if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
				// On UI thread.
				getMap().animateCamera(CameraUpdateFactory.newLatLng(pos));
			} else {
				// Not on UI thread.
				myHandler.post(new Runnable() {

					@Override
					public void run() {
						getMap().animateCamera(
								CameraUpdateFactory.newLatLng(pos));
					}
				});
			}
			return true;
		}
		return false;
	}

	@Override
	public boolean clearAllOverlays() {
		try {
			for (Overlay o : getOverlays()) {
				o.clear();
			}
			this.getOverlays().clear();
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	private ArrayList<Overlay> getOverlays() {
		return overlays;
	}

	@Deprecated
	private Overlay generateOverlayFrom(OverlayOptions mapOverlay) {
		Overlay result = new Overlay(this);
		for (MarkerOptions markerOptions : mapOverlay) {
			result.add(getMap().addMarker(markerOptions));
		}
		return result;
	}

	@Override
	public boolean removeOverlay(Overlay overlay) {
		try {
			overlay.clear();
			return this.getOverlays().remove(overlay);
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	@Override
	public Overlay addNewEmptyOverlay(int defaultIconId) {
		if (getMap() == null) {
			Log.e(LOG_TAG, "map was null, mapview not yet initialized");
			return null;
		}
		return new Overlay(this, defaultIconId);
	}

	@Override
	public Overlay generateOverlay(OverlayOptions overlayOptions) {
		Overlay overlay = generateOverlayFrom(overlayOptions);
		getOverlays().add(overlay);
		return overlay;
	}

	@Override
	public int getZoomLevel() {
		return (int) getMap().getCameraPosition().zoom;
	}

	@Override
	public boolean setZoomLevel(int level) {
		getMap().animateCamera(CameraUpdateFactory.zoomTo(level));
		return true;
	}

	@Override
	public void onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
			float distanceY) {
	}

	@Override
	public void onLongPress(MotionEvent e) {

		LatLng gpsPos = getLatLngPosFromClickEvent(e);
		eventListener.onLongPress(getActivity(), getMap(), gpsPos);
	}

	@Override
	public void onSingleTab(MotionEvent e) {

		LatLng gpsPos = getLatLngPosFromClickEvent(e);
		eventListener.onSingleTab(getActivity(), getMap(), gpsPos);
	}

	public void enableZoomButtons(boolean showThem) {
		getMap().getUiSettings().setZoomControlsEnabled(showThem);
	}

	@Override
	public void onDoubleTap(MotionEvent e) {
		LatLng gpsPos = getLatLngPosFromClickEvent(e);
		eventListener.onDoubleTab(getActivity(), getMap(), gpsPos);
	}

	public LatLng getLatLngPosFromClickEvent(MotionEvent e) {
		return getLatLngForPixelPos((int) e.getX(), (int) e.getY());
	}

	private void onNewAreaOnMapIsShown() {
		LatLng topLeft = getMap().getProjection().fromScreenLocation(
				new Point(0, 0));
		LatLng bottomRight = getMap().getProjection().fromScreenLocation(
				new Point(getContainerView().getWidth(), getContainerView()
						.getHeight()));
		eventListener.onNewAreaOnMapIsShown(topLeft, bottomRight,
				getZoomLevel());
	}

	public void setSatellite(boolean b) {
		if (b) {
			getMap().setMapType(GoogleMap.MAP_TYPE_HYBRID);
		} else {
			getMap().setMapType(GoogleMap.MAP_TYPE_NORMAL);
		}

	}

	private void showUserLocation(boolean userPositionDisplayed) {
		getMap().setMyLocationEnabled(userPositionDisplayed);
	}

	@Override
	public void onViewCreated(View view, Bundle savedInstanceState) {
		super.onViewCreated(view, savedInstanceState);
		// now the map is ready
		if (firstTimeThisMapIsShown) {
			getMap().setOnMyLocationChangeListener(eventListener);
			getMap().setOnMarkerClickListener(this);
			getMap().setOnMarkerDragListener(this);
			eventListener.onMapViewIsReadyForTheFirstTime(this, getActivity(),
					getMap());
			firstTimeThisMapIsShown = false;
		} else {
			Log.d(LOG_TAG,
					"Map view was restored, not calling onMapViewIsReady(..)");
			Log.d(LOG_TAG, "    > getMap() " + getMap());
		}
	}

	@Override
	public boolean onMarkerClick(Marker marker) {
		MarkerListener listener = listeners.get(marker);
		if (listener != null) {
			return listener.onTab(marker);
		}
		return false;
	}

	@Override
	public void onMarkerDragStart(Marker marker) {
		sendDragEventToMarkerListener(marker, MarkerListener.DRAG_START);
	}

	@Override
	public void onMarkerDrag(Marker marker) {
		sendDragEventToMarkerListener(marker, MarkerListener.DRAG);
	}

	@Override
	public void onMarkerDragEnd(Marker marker) {
		sendDragEventToMarkerListener(marker, MarkerListener.DRAG_END);
	}

	private void sendDragEventToMarkerListener(Marker marker, final int type) {
		MarkerListener listener = listeners.get(marker);
		if (listener != null) {
			listener.onDrag(marker, type);
		}
	}

	public static boolean initDefaultBehavior(
			final GoogleMapsV2View googleMapsV2View) {
		if (googleMapsV2View.getMap() == null) {
			Log.e(LOG_TAG, "Cant be called before the map is initialized!");
			return false;
		}
		googleMapsV2View.setSatellite(true);
		googleMapsV2View.setZoomLevel(17);
		googleMapsV2View.showUserLocation(true);
		new SimpleAsyncTask() {

			@Override
			public void onRun() {
				googleMapsV2View.setMapCenterTo(new GeoUtils(googleMapsV2View
						.getActivity()).getLastKnownPosition());
			}
		}.run();
		return true;
	}

	public void setMapCenterTo(Location location) {
		setMapCenterTo(new LatLng(location.getLatitude(),
				location.getLongitude()));
	}

}
